Compile time Calculate

컴파일 타임 계산
컴파일 타임 계산은 두가지 방법으로 구현할 수 있다.

- 템플릿 메타 함수(Meta-Function)
- constexpr

두 방법 모두 하위 호환성(backward compatibility)을 갖는다.
constexpr은 C++11에서 도입되었으며, C++14에서 기능이 확장되었다.
컴파일 타임 함수(C++11)
피보나치 수(재귀함수)
constexpr long fibonacci(long n){
return n<=2?1:fibonacci(n-1)+fibonacci(n-2);
}
C++11에서 constexpr은 대부분 단일 return 문이다.
constexpr에는 계산이 없는 특정 문장(statement)으로
비어있는 문장, (특정)static_assert, 타입 정의, using 선언 및 지시장 등이 있다.
(정규 함수에 비해 constexpr에 들어갈 수 있는 문장은 다소 제한적임)

- 함수 외부에서는 아무것도 읽거나 쓸 수 없다. 사이드 이펙트(side effect)가 없다
- 변수를 포함할 수 없다.
- if 또는 for문과 같은 제어문을 포함할 수 없다.
- 단일 계산문만 포함할 수 있다.
- constexpr인 함수만 호출 가능하다.

템플릿 메타 함수에 비하면, constexpr은 좀 더 융통성이 있다.
- 부동소수점 타입을 전달할 수 있다.
- (컴파일 타임에 처리할 수 있는 경우) 사용자 정의 타입을 처리할 수 있다.
- 타입 검출을 사용할 수 있다.
- 멤버 함수를 정의할 수 있다.
- (특수화보다 간단한) 조건문을 쓸 수 있다.
- 런타임 인수를 사용해 함수를 호출할 수 있다.
constexpr double squre(double x){
return x*x;
}
부동소수점 타입은 템플릿 인수로 사용할 수 없으며,
C++11 이전에는 부동소수점을 이용해서 컴파일 타임 계산을 수행할 수 없었다.
template <typename T>
constexpr T squre(T x){
return x*x;
}
int main(int argc, char* argv[]){
long n=atoi(argv[1]);
cout<<"Fibonacci(" <<n<< ")= "<<Fibonnaci(n)<<'\n';
return 0;
}
위의 코드에서 명령줄로부터 첫 번째 인수(n)을 전달했다.
(컴파일 중에 첫 번째 인수가 무엇인지 알 수 없음)
하나 이상의 인수가 있다는 사실은 런타임 때만 알 수 있기 떄문에 컴파일 타임에 함수를 계산할 수 없다.

constexpr 함수의 하이브리드 적용 범위는 해당 매개변수를 차례대로 constexpr 함수에만 전달할 수 있다.
정규 함수에 매개변수를 전달하면 컴파일 타임에서의 사용을 방해한다.
static_assert와 같이 컴파일 타임에 계산하는 함수에 함수 매개변수를 전달할 수 없다.(런타임 호출을 막음)
따라서 C++11에서는 constexpr 함수내에 assert()를 사용할 수 없었다.
최신 컴파일러는 assert를 C++14의 constexpr로 제공하기 때문에 매개변수로 사용가능
C++ 표준은 표준 라이브러리에 있는 함수 중에서 어떤 함수가 constexpr로 구현해야 하는지를 규정한다.
일부 라이브러리에서는 추가 함수를 constexpr로 구현한다.
constexpr long floor_sqrt(long n){
return floor(sqrt(n));
}
확장된 컴파일 타임 함수(C++14)
컴파일 함수에 대한 제한은 C++14에서 합리적인 수준까지 완화되었다.
C++14 부터 아래의 함수도 constexpr로 정의할 수 있다.
- void 함수
constexpr void sqruare(int& x){ x*=x; }

- 초기화 되어있는 지역변수
- static이 아니고, thread 저장 기간이 없는 지역 변수
- 리터럴 타입을 갖는 지역변수

- goto, 어셈블리 코드(asm 블록), try 블록을 제외한 제어문
// power
template <typename T>
constexpr T power(const T& x, int n){
T r(1);
while(--n>0) r*=x;
return r;
}
// popcount ( 1 )
constexpr size_t popcount(size_t x){
int count=0;
for(; x!=0; ++count) x&=x-1;
return count;
}
위의 popcount() 함수는 for문을 포함하고 있기 때문에 C++11에서 사용할 수 없다.
for문 대신 재귀함수를 사용하면, C++11에서도 사용할 수 있다.
constexpr size_t popcount(size_t x){
return x==0?0:popcount(x&x-1)+1;
}
소수 판정 함수
컴파일 타임에 주어진 숫자가 소수인지 판별하는 함수
constexpr bool is_prime(int i){
if(i==1) return false;
if(i%2==0) return i==2;
for(int j=3; j<i; j+=2){
if(i%j==0) return false;
}
return true;
}
매개변수 i의 제곱근보다 작은 홀수를 검사해도 동일함
제곱근 범위 나누기)
for문을 통해서 값을 계산할 때, sqrt() 값에 대하여 좌우 대칭이기 떄문에
sqrt() 보다 작은 값에 대하여서 소수임을 검사하면 됨
constexpr bool is_prime(int i){
if(i==1) return false;
if(i%2==0) return i==2;
int max_check=static_cast<int>(sqrt(i))+1;
for(int j=3; j<max_check; j+=2){
if(i%j==0) return false;
}
return true;
}
위 코드에서 sqrt() 함수가 constexpr로 구현되어 있는 표준 라이브러리(g++ 4.7-4.9)에서만 동작한다.
constexpr 문에서 사용할 수 없다.
다른 컴파일러에서는 위의 코드처럼 sqrt() 함수를 사용하기 위해서는 constexpr로 직접 구현해야 한다.
constexpr int square_root(int x){
double r=x, dx=x;
while(const_abs((r*r)-dx)>0.1){
r=(r+dx/r)/2;
}
return static_cast<int>(r);
}
constexpr bool is_prime(int i){
if(i==1) return false;
if(i%2==0) return i==2;
int max_check=square_root(i)+1;
for(int j=0; j<max_check; j+=2){
if(i%j==0) return false;
}
return true;
}
C++11에서는 constexpr 함수에 제어문을 사용할 수 없다.
제어문 대신 '? :' 문을 사용한다.
constexpr bool is_prime_aux(int i, int div){
return div==i?true:(i%div==0?false:is_prime_aux(i, div+2));
}
constexpr bool is_prime(int i){
return i==1?false:(i%2==0?i==2:is_prime_aux(i, 3));
}
이론상 C++11의 constexpr으로 모든 계산을 구현할 수 있다.
constexpr는 상수 함수, 바로 뒤의 원소 함수(successor function), 투영 및 재귀함수의 모든 기능을 제공한다.

하지만, 실제로는 제한된 표현력으로 복잡한 계산을 구현하기 매우 어렵다.

constexpr가 도입되기 전에는 템플릿 메타 함수(Meta-Function)을 이용해서 컴파일 타임 계산을 구현했다.
템플릿 메타 함수는 float나 사용자 정의 타입을 사용할 수 없는 등 더 제한되어 있었다.
const & constexpr 변수
멤버가 아닌 변수가 const일 때,
const int i=somethings;
위 선언은 두 가지 수준의 불변성을 설정할 수 있다.
- 프로그램 실행 중에 개체를 변경할 수 없다.(항상 성립)
- 컴파일 타임에 개체의 값을 이미 알고 있다.(상황에 따라 성립)

컴파일 도중 i의 값을 사용할 수 있는지의 여부는 할당한 표현식에 따라 다르다.
만일 something이 리터럴인 경우,
const long i=7, j=8;
컴파일 하는 동안 값을 사용할 수 있다
템플릿 매개변수는 컴파일 타이에 값이 리터럴로 정해져야 한다.
template <typename T, unsigned int T_SIZE>
class forTest{
private:
T* m_data;
public:
forTest(void){
m_data=new T[T_SIZE];
}
~forTest(void){
reset();
}
void reset(void){
delete[] m_data;
m_data=nullptr;
}
// ...
}
int main(void){
forTest<double, 100> test1;
int a=100;
forTest<double, a> test2; // error;
const int b=100;
forTest<double, b> test3; // OK; const
}
템플릿 매개변수
template <long N>
struct static_long{
static const long value=N;
};
int main(void){
// ...
const long k=10;
static_long<k> sk; // OK
long ll;
cin>>ll;
const long cl=ll;
static_long<cl> scl; // error
// ...
}
컴파일 타임 상수로 이뤄진 간단한 표현식은 보통 컴파일 타임에 이용 가능하다.

프로그램 소스에서 어떤 종류의 상수를 갖고 있는지 일반적으로 말할 수 없는 경우가 있다.
const long ri=floor(sqrt(i));
static_long<ri> sri; // g++ 4.7-4.9
ri는 표준 라이브러리에 있는 sqrt와 floor가 constexpr일 경우(g++ 4.7-4.9) 컴파일 타임에 알 수 있다.
그렇지 않으면, 템플릿 인수로 사용할 때 오류가 발생한다.

상수가 컴파일 타임에 값을 갖도록 하기 위해서는 constexpr를 선언해야 한다.
constexpr long ri=floor(sqrt(i)); // g++ 4.7-4.9
위처럼 constexpr로 선언된 변수 ri는 컴파일 중 값을 알 수 있다.
컴파일 중 알 수 없는 경우, 해당 줄은 에러를 출력하고 컴파일되지 않음

constexpr 변수는 constexpr 함수보다 더욱 엄격하다.
constexpr 변수는 컴파일 타임 값만 허용하고, constexpr 함수는 컴파일 타임 및 런타임 인자를 모두 허용한다.

constexpr 함수의 인자들이 constexpr 규칙에 부합하지 못하는 경우,
컴파일 타임에 실행되지 못하고 런타임에 실행된다.